<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>

  <script>  // callback ==>
    Function.prototype.call = function (obj, ...args) {
      console.log('call()')
      // this(...args)
      if (obj===null || obj===undefined) {
        // obj = window
        return this(...args)
      }

      // 给obj添加一个指向当前函数的临时方法
      obj.tempFn = this
      // 通过obj调用临时方法
      const result = obj.tempFn(...args)
      // 删除这个临时方法
      delete obj.tempFn
      // 返回执行结果
      return result
    }  

    // 自定义call工具函数
    function call(fun, obj, ...args) {
      if (obj===null || obj===undefined) {
        // obj = window
        return fun(...args)
      }

      // 给obj添加一个指向当前函数的临时方法
      obj.tempFn = fun
      // 通过obj调用临时方法
      const result = obj.tempFn(...args)
      // 删除这个临时方法
      delete obj.tempFn
      // 返回执行结果
      return result
    }

    function apply(fun, obj, args) {
      if (obj===null || obj===undefined) {
        // obj = window
        return fun(...args)
      }

      // 给obj添加一个指向当前函数的临时方法
      obj.tempFn = fun
      // 通过obj调用临时方法
      const result = obj.tempFn(...args)
      // 删除这个临时方法
      delete obj.tempFn
      // 返回执行结果
      return result
    }

    /* 
    返回一个新函数, 新函数内部会调用原函数, 且this为bind指定的对象
    */
    function bind(fn, obj, ...args) {
      return (...args2) => {
        return call(fn, obj, ...args, ...args2) // 优先考虑bind指定的参数, 其次考虑新函数的参数
      }
    }
    
  </script>

  <script>
    function fn(a, b) {
      console.log(this, a, b)  // 对象快照: 打印时对象的情况
      return a + b
    }
    const obj = {m: 1}
    // console.log(fn.call(obj, 2, 3))
    // console.log(fn.call(undefined, 2, 3))

    // console.log(call(fn, obj, 2, 3))
    // console.log(call(fn, undefined, 2, 3))
    // console.log(apply(fn, obj, [2, 3]))
    // console.log(apply(fn, undefined, [2, 3]))


    // console.log(fn.bind(obj)(2, 3))
    // console.log(fn.bind(obj, 4)(2, 3))
    // console.log(fn.bind(obj, 4, 5)(2, 3))
    console.log(bind(fn, obj)(2, 3))
    console.log(bind(fn, obj, 4)(2, 3))
    console.log(bind(fn, obj, 4, 5)(2, 3))
  </script>
</body>
</html>